
import { sleep, amapReady } from '../utils'
interface DataItem {
  name: string
  lnglat: [number, number]
}

const createMarkerLabel = (name: string, count: number) => {
  const labelContent = `<div class="amp-charts-label-style" style="font-weight:700;">${name}营销部<br /><span style="font-weight:500;">零售户:${count}户</span></div>`
  return {
    direction: 'bottom-right',
    offset: new AMap.Pixel(10, 10), //设置文本标注偏移量
    content: labelContent //设置文本标注内容
  }
}

const arrNotEmpty = <T>(v?: Array<T> | null | undefined): v is Array<T> => {
  return Array.isArray(v) && v.length > 0
}

enum AreaLevel {
  country = 1,
  province,
  city,
  district
}

class AreaNode {
  adcode!: string
  info: any
  polygons!: any[]
  marker?: any
  markerList?: any[]
  parent?: any
  children?: AreaNode[]
  colorIndex!: number
  fillColor!: string
  hoverColor!: string
  level: AreaLevel = AreaLevel.province
  onDataChange(data: DataItem[], map: any) {
    if (Array.isArray(this.children) && this.children.length > 0) {
      this.children.forEach((i) => i.onDataChange(data, map))
    } else {
      const list = data.filter((d) => AMap.GeometryUtil.isPointInPolygon(d.lnglat, this.info.boundaries))
      this.markerList = list.map((data) => {
        const m = new AMap.Marker({
          position: data.lnglat,
          label: {
            direction: 'bottom-center',
            offset: new AMap.Pixel(0, 10), //设置文本标注偏移量
            content: data.name
          }
        })
        map.add(m)
        m.hide()
        return m
      })
    }
    this.marker?.setLabel(createMarkerLabel(this.info.name, this.total))
  }

  onColorChange(colors: string[]) {
    this.fillColor = colors[this.colorIndex % (colors.length - 1)]
    this.polygons.forEach((p: any) => p.setOptions({ fillColor: this.fillColor }))
    this.children?.forEach((c) => c.onColorChange(colors))
  }

  onHoverColorChange(color: string) {
    this.hoverColor = color
    this.children?.forEach((c) => c.onHoverColorChange(color))
  }

  get total(): number {
    if (Array.isArray(this.children) && this.children.length > 0) {
      return this.children.reduce((prev, curr) => prev + curr.total, 0)
    }
    return this.markerList?.length || 0
  }
  constructor(options: Partial<AreaNode>) {
    Object.assign(this, options)
    this.polygons?.forEach?.((polygon: any) => {
      polygon.on('mouseover', () => {
        this.polygons.forEach((p: any) => p.setOptions({ fillColor: this.hoverColor }))
      })
      polygon.on('mouseout', () => {
        this.polygons.forEach((p: any) => p.setOptions({ fillColor: this.fillColor }))
      })
    })
  }
}

interface KeyValue {
  [K: string]: any
}
export class DataRundownRender {
  private map: any = null
  private running = true
  private districtSearch: any = null
  private colors = ['#ffffcc', '#a1dab4', '#41b6c4', '#225ea8']
  private hoverColor = '#61da24'
  private colorIdx = 0
  private readonly searchCache: KeyValue = {}
  private root: AreaNode | null = null
  private currArea: AreaNode | null = null
  private data: DataItem[] = []
  private runtime = false
  constructor(runtime = false) {
    this.runtime = runtime
  }
  async init(container: HTMLElement, border: HTMLElement, mapStyle: string) {
    try {
      await sleep()
      if (!this.running) return
      await amapReady()
      await this.renderMap(container, mapStyle)
      await this.renderDistrict()
      this.root = new AreaNode({ adcode: '100000', info: {}, polygons: [], children: [], colorIndex: 0, level: AreaLevel.country })
      await this.initProvince('150000')
      this.root = this.root.children![0]
      this.root && (this.root.parent = null)
      this.currArea = this.root
      this.root?.onDataChange?.(this.data, this.map)
      this.root?.onColorChange?.(this.colors)
      this.root?.onHoverColorChange?.(this.hoverColor)
      this.render()
    } catch (err: any) {
      console.log('render init error')
      console.log(err)
    }
  }
  setMapStyle(style: string) {
    const styleName = 'amap://styles/' + style || 'blue'
    this.map?.setMapStyle?.(styleName)
  }
  setData(data: DataItem[]) {
    this.data = data
    this.root?.onDataChange?.(this.data, this.map)
  }
  setColors(colors: string[]) {
    this.colors = colors
    this.root?.onColorChange?.(this.colors)
  }
  setHoverColor(hoverColor: string) {
    this.hoverColor = hoverColor
    this.root?.onHoverColorChange?.(this.hoverColor)
  }
  render() {
    if (!this.currArea) return
    if (this.currArea.level == AreaLevel.district) {
      this.currArea.polygons.forEach((i) => i.show())
      if (Array.isArray(this.currArea.markerList) && this.currArea.markerList.length > 0) {
        this.currArea.markerList?.forEach((i) => i.show())
      } else {
        this.currArea.marker?.show()
      }
    } else {
      if (arrNotEmpty(this.currArea.children)) {
        this.currArea.children?.forEach?.((i) => {
          i.marker?.show()
          i.polygons.forEach((p) => p.show())
        })
      } else {
        this.currArea.marker?.show?.()
        this.currArea.polygons.forEach((p) => p.show())
      }
    }
    this.map.setFitView()
  }

  private hideCurr() {
    if (!this.currArea) return
    if (this.currArea.level == AreaLevel.district) {
      this.currArea.polygons.forEach((i) => i.hide())
      if (Array.isArray(this.currArea.markerList) && this.currArea.markerList.length > 0) {
        this.currArea.markerList?.forEach((i) => i.hide())
      } else {
        this.currArea.marker?.hide()
      }
    } else {
      if (arrNotEmpty(this.currArea.children)) {
        this.currArea.children?.forEach?.((i) => {
          i.marker?.hide()
          i.polygons.forEach((p) => p.hide())
        })
      } else {
        this.currArea.marker?.hide?.()
        this.currArea.polygons.forEach((p) => p.hide())
      }
    }
  }

  back() {
    if (!this.currArea || !this.currArea.parent) return
    this.hideCurr()
    this.currArea = this.currArea.parent
    this.render()
  }
  private findNode(code: string) {
    if (!this.root) return null
    if (!code || code == this.root.adcode) {
      return this.root
    }
    if (!this.root?.children) return null
    const pathes: { list: AreaNode[]; idx: number }[] = [{ list: this.root.children, idx: 0 }]
    let curr = pathes.pop()
    while (curr) {
      for (let i = curr.idx; i < curr.list.length; i++) {
        const item = curr.list[i]
        if (code == item.adcode) {
          return item
        }
        if (Array.isArray(item.children) && item.children.length > 0) {
          pathes.push({ list: curr.list, idx: i + 1 })
          pathes.push({ list: item.children, idx: 0 })
          break
        }
      }
      curr = pathes.pop()
    }
    return null
  }
  go(code: string) {
    this.hideCurr()
    this.currArea = this.findNode(code)
    this.render()
  }

  destory() {
    this.running = false
    this.map?.destroy?.()
    this.map = null
  }

  private async renderMap(container: HTMLElement, mapStyle: string) {
    const map = new AMap.Map(container, {
      mapStyle: `amap://styles/${mapStyle || 'blue'}`,
      pitch: 0,
      features: ['bg', 'road'],
      zoom: 9,
      viewMode: '2D'
    })
    map.on('rightclick', () => {
      this.back()
    })
    this.map = map
  }

  private async search(level: 'province' | 'city' | 'district', code: string) {
    let cache = this.searchCache[code]
    if (cache?.adcode) {
      return Promise.resolve(cache)
    }
    this.districtSearch.setLevel(level)
    return new Promise((resolve) =>
      this.districtSearch.search(code, (status: any, res: any) => {
        if(status != 'complete') {
          console.log('search error')
          console.log(status)
          console.log(res)
          print()
        }
        const first = res?.districtList?.[0]
        if (first?.adcode) {
          this.searchCache[code] = first
          resolve(first)
        } else {
          resolve([])
        }
      })
    )
  }
  private searchProvince(code: string) {
    return this.search('province', code)
  }

  private searchCity(code: string) {
    return this.search('city', code)
  }

  private searchDistrict(code: string) {
    return this.search('district', code)
  }

  async renderDistrict() {
    await new Promise((resolve) => AMap.plugin('AMap.DistrictSearch', resolve))
    this.districtSearch = new AMap.DistrictSearch({
      level: 'province', //关键字对应的行政区级别，country 表示国家
      subdistrict: 1, //显示下级行政区级数，1表示返回下一级行政区
      extensions: 'all', //返回行政区边界坐标组等具体信息
      showbiz: false
    })
  }

  private createPolygons(data: any, fillColor: string, onClick: () => void) {
    const strokeColor = fillColor
    const polygonList = data.boundaries.map((boundary: any) => {
      const polygon = new AMap.Polygon({
        path: boundary, //路径
        fillColor, //多边形填充颜色
        strokeWeight: 2, //线条宽度，默认为 2
        strokeColor //线条颜色
      })
      polygon.on('rightclick', () => {
        this.back()
      })
      polygon.on('click', onClick)
      this.map.add(polygon)
      return polygon
    })
    return polygonList
  }

  private createMarker(data: any) {
    const marker = new AMap.Marker({
      position: data.center,
      label: createMarkerLabel(data.name, 0)
    })
    this.map.add(marker)
    return marker
  }
  private createDistrictNode(districtData: any, parent: AreaNode): AreaNode {
    const colorIndex = this.colorIdx++
    const color = this.colors[colorIndex % (this.colors.length - 1)]
    let node: AreaNode
    const polygons: any[] = this.createPolygons(districtData, color, () => {
      this.go(districtData.adcode)
    })
    const marker: any = this.createMarker(districtData)
    const markerList: any[] = []
    polygons.forEach((p: any) => p.hide())
    marker.hide()
    node = new AreaNode({
      level: AreaLevel.district,
      adcode: districtData.adcode,
      info: districtData,
      children: [],
      polygons,
      colorIndex,
      marker,
      parent,
      markerList,
      fillColor: color,
      hoverColor: this.hoverColor
    })
    return node
  }

  private createCityNode(cityData: any, parent: AreaNode): AreaNode {
    const colorIndex = this.colorIdx++
    const color = this.colors[colorIndex % (this.colors.length - 1)]
    let node: AreaNode
    const polygons: any[] = this.createPolygons(cityData, color, () => {
      this.go(cityData.adcode)
    })
    const marker: any = this.createMarker(cityData)
    polygons.forEach((p: any) => p.show())
    marker.show()
    node = new AreaNode({
      level: AreaLevel.city,
      adcode: cityData.adcode,
      info: cityData,
      children: [],
      polygons,
      colorIndex,
      marker,
      parent,
      fillColor: color,
      hoverColor: this.hoverColor
    })
    return node
  }

  private async initProvince(code: string) {
    const data = await this.searchProvince(code)
    if (!data.adcode) return
    const provinceNode: AreaNode = new AreaNode({
      level: AreaLevel.province,
      adcode: code,
      info: data,
      children: [],
      polygons: [],
      colorIndex: 0
    })
    this.root?.children?.push(provinceNode)

    const cityList = data.districtList
    if (!Array.isArray(cityList) || cityList.length < 1) {
      return
    }

    for (const city of cityList) {
      const cityData = await this.searchCity(city.adcode)
      if (!cityData?.adcode) continue
      provinceNode.children?.push(this.createCityNode(cityData, provinceNode))
      this.map.setFitView()
    }
    if (!this.runtime) {
      return
    }
    for (const cityNode of provinceNode.children!) {
      if (cityNode.adcode != '152500') continue
      for (const dist of cityNode.info!.districtList) {
        const distData = await this.searchDistrict(dist.adcode)
        if (!distData?.adcode) continue
        cityNode.children?.push(this.createDistrictNode(distData, cityNode))
      }
      break
    }
    this.map.setFitView()
  }
}
